//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: sw_cin2tab.c 822 2007-09-07 06:41:13Z roger $
//

/*
** SWin -- sw_cin2tab
** Utility for converting .cin to Generic Table's binary format
** Copyright (C) 2001 Roger So, Sun Wah Linux Ltd
**
** $Id: sw_cin2tab.c 822 2007-09-07 06:41:13Z roger $
*/

#define IM_TBL_SIG "UnihanGenTab1"

#define MAX_ENAME_LEN 20
#define MAX_CNAME_LEN 16

#define idx(x) (((unsigned char)x) - 0x20)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <iconv.h>
#include <errno.h>
#include "const.h"

#define FALSE 0
#define TRUE 1
#define PRIO_CELL 3

#define DEBUG_CIN 0

typedef struct _PEKey {
	char name[7];
} PEKey;


typedef struct _MaxPrio {
	int maxPrio;
	struct _MaxPrio *next;
} MaxPrio;

typedef struct _CharDef {
	char key[256];
	char value[256];
	struct _CharDef *prev;
	struct _CharDef *next;
} CharDef;

typedef struct _FastIdx {
	int offset;
} FastIdx;

typedef int Priority;

typedef struct _KeyValTable {
	int keyoff;
	int valoff;
	int frqoff;
	int maxfrqoff;
} KeyValTable;

CharDef *chardef = NULL;
CharDef *chardef_end = NULL;

PEKey pekey[0x80-0x20];
FastIdx fastidx[0x80-0x20];
KeyValTable *keyvaltab;
Priority *priority = NULL;
MaxPrio *maxprio = NULL;

char ename[64];
char cname[64];
int preedit_max = 0;
int num_key = 0;
int num_val = 0;
int num_max = 0;
int idx_key_tbl_len = 0;
int idx_val_tbl_len = 0;

int keyused[0x60];

unsigned long hints = 0;

iconv_t iconvd;
int need_iconv = 1;

const char *exec_name;

/* Function prototypes */
void handle_ename(FILE *, char *);
void handle_cname(FILE *, char *);
void handle_keyname(FILE *, char *);
void handle_chardef(FILE *, char *);
void create_idx(void);
int parse_cinfile(FILE *);
int write_tabfile(FILE *);
int write_frqfile(char *);
void print_usage(void);
void create_frq(void);

void
handle_ename(FILE *cinfile, char *arg)
{
	strcpy(ename, arg);
}

void
handle_preedit_max(FILE *cinfile, char *arg)
{
	preedit_max = atoi(arg);
	hints |= SELECT_AFTER_MAX_PREEDIT;
}

void
handle_cname(FILE *cinfile, char *arg)
{
	char *tmpcnamesave, *tmpcname = NULL;
	int outbytesleft = 64;
	int inbytesleft = strlen(arg);

	tmpcnamesave = tmpcname = (char *)malloc(sizeof(char)*64);

	if (need_iconv) {
		iconv(iconvd, &arg, &inbytesleft, &tmpcname, &outbytesleft);
		tmpcnamesave[outbytesleft] = 0;
		strcpy(cname, tmpcnamesave);
	} else {
		strcpy(cname, arg);
	}
	free(tmpcnamesave);
}

void 
handle_keyname(FILE *cinfile, char *arg)
{
	char key[64], *namesave, *name = NULL;
	char *tmpnamesave, *tmpname = NULL;
	int inbytesleft;
	int outbytesleft = 64;

	if (strcmp("begin", arg)!=0) {
		printf("parse error\n");
		return;
	}

	memset(pekey, 0, (0x80-0x20)*sizeof(PEKey));

	namesave = name = (char *)malloc(sizeof(char)*64);
	memset(name, 0, sizeof(char)*64);

	while (fscanf(cinfile, "%s %s", key, namesave) == 2) {
		if (!strcmp("%keyname", key) &&
		    !strcmp("end", namesave))
			break;

		if (need_iconv) {
			name = namesave;
			inbytesleft = strlen(name);
			tmpnamesave = tmpname = (char *)malloc(sizeof(char)*64);
			memset(tmpname, 0, 64);
			outbytesleft = 64;
			errno = 0;
			iconv(iconvd, &name, &inbytesleft, &tmpname, &outbytesleft);
			if (errno) {
				printf("error converting to UTF-8! ");
				printf("(%s %s)\n", key, namesave);
				return;
			}
		
			tmpnamesave[6] = 0;
			strcpy(pekey[idx(key[0])].name, tmpnamesave);
			free(tmpnamesave);
		} else {
			strncpy(pekey[idx(key[0])].name, namesave, 7);
		}
		memset(namesave, 0, sizeof(char)*64);
	}

	free(namesave);
	return;
}

void
handle_chardef(FILE *cinfile, char *arg)
{
	char *key = NULL, *valuesave = NULL, *value = NULL;
	char *tmpvalsave = NULL, *tmpval = NULL;
	int i, inbytesleft, outbytesleft;
	CharDef *newcd;
#if DEBUG_CIN
	FILE *pf;
#endif
	
	if (strcmp("begin", arg)!=0) {
		printf("parse error\n");
		return;
	}

	memset(keyused, 0, sizeof(int)*0x60);

	key = (char*)malloc(sizeof(char)*256);
	valuesave = value = (char*)malloc(sizeof(char)*256);
	tmpvalsave = tmpval = (char*)malloc(sizeof(char)*256);

	memset(key, 0, 256);
	memset(value, 0, 256);

	while (fscanf(cinfile, "%s %[^\n]", key, valuesave) == 2) {
		if (!strcmp("%chardef", key) &&
		    !strcmp("end", valuesave))
			break;

		value = valuesave;
		tmpval = tmpvalsave;
		if (need_iconv) {
			inbytesleft = strlen(value);
			memset(tmpval, 0, 256);
			outbytesleft = 256;
			errno = 0;
			iconv(iconvd, &value, &inbytesleft, &tmpval, &outbytesleft);
			if (errno) {
				printf("error converting to UTF-8! ");
				printf("(%s %s)\n", key, valuesave);
				//free(tmpvalsave);
				continue;
			}
		} else {
			strcpy(tmpvalsave, value);
		}
		tmpvalsave[255] = 0;
		
		newcd = (CharDef*)malloc(sizeof(CharDef));
		if (!chardef) {
			chardef = newcd;
			chardef_end = newcd;
			newcd->prev = NULL;
			newcd->next = NULL;
		} else {
			CharDef *cd = chardef_end;
			CharDef *cd_next = NULL;
		
			/* insert newcd following dictionary sort */ 
			while (cd) {
				if (strcmp(key, cd->key) >= 0)
					break;
				cd_next = cd;
				cd = cd->prev;
			}

			newcd->prev = cd;
			if (cd) {
				cd->next = newcd;
			} else {
				chardef = newcd;
			}
			newcd->next = cd_next;
			if (cd_next) {
				cd_next->prev = newcd;
			} else {
				chardef_end = newcd;
			}
			/* **************************************** */
		}
		
		strcpy(newcd->key, key);
		strcpy(newcd->value, tmpvalsave);

		for (i = 0; i < strlen(key); i++) {
			if ((unsigned char)key[i] < 0x20 ||
			    (unsigned char)key[i] >= 0x80)
				continue;
			keyused[idx(key[i])] = 1;
		}

		num_val++;
			
		idx_val_tbl_len += strlen(tmpvalsave) + 1;

		//free(tmpvalsave);
	}

	free(tmpvalsave);
	free(valuesave);
	free(key);
	
	return;
}

void
create_frq(void)
{
	CharDef *chardef_end = chardef->next;
	MaxPrio *prio_end = maxprio;
	int num_val_last, num_val_current;
#if DEBUG_CIN
	FILE *pf;
#endif

	num_val_last = 0;
	num_val_current = 1;

	while(chardef_end) {
		num_val_current++;
		if (strcmp (chardef_end->key, chardef_end->prev->key) != 0) {
			if (!maxprio) {
				maxprio = (MaxPrio *)malloc(sizeof(MaxPrio));
				if (maxprio) {
					maxprio->maxPrio = (num_val_current - 1) * PRIO_CELL;
					maxprio->next = NULL;
				}
				prio_end = maxprio;
				num_val_last = num_val_current;
				/* equare to (num_val_last = num_val - 1) */
			}
			else {
				prio_end->next = (MaxPrio *)malloc(sizeof(MaxPrio));
				if (prio_end->next) {
					prio_end->next->maxPrio = (num_val_current - num_val_last) * PRIO_CELL;
					/* equare to (num_val - 1 - num_val_last) */
					prio_end = prio_end->next;
					prio_end->next = NULL;
					num_val_last = num_val_current;
				}
			}
		}
		chardef_end = chardef_end->next;
	}

	/* I don't think there will be only one key defined */
	prio_end->next = (MaxPrio *)malloc(sizeof(MaxPrio));
	if (prio_end->next) {
		prio_end->next->maxPrio = (num_val_current - num_val_last + 1) * PRIO_CELL;
		prio_end = prio_end->next;
		prio_end->next = NULL;
	}
	priority = (Priority *)malloc(num_val * sizeof(Priority));
	memset(priority, 0, num_val * sizeof(Priority));
}

void
create_idx(void)
{
	int j, i = 0;
	int curr_keyoff = 0;
	int curr_valoff = 0;
	char *currkey, *lastkey = NULL;
	char maxkey;
	CharDef *cd = chardef;
	MaxPrio *mp = maxprio;
	int firstidx;
	int maxoff = 0;
#if DEBUG_CIN
	FILE *pf;
#endif

	keyvaltab = (KeyValTable*)malloc(sizeof(KeyValTable)*num_val);
	memset(keyvaltab, 0, sizeof(KeyValTable)*num_val);

	memset(fastidx, 0, sizeof(FastIdx)*(0x80-0x20));

	/* chardef must be sorted */

	/* first key definition */
	currkey = cd->key; maxkey = currkey[0];
	firstidx = idx(currkey[0]);
	fastidx[idx(currkey[0])].offset = 0;
	keyvaltab[0].keyoff = 0;
	keyvaltab[0].valoff = 0;
	keyvaltab[0].frqoff = 0;
	if (mp->maxPrio > 1 * PRIO_CELL) {
		keyvaltab[0].maxfrqoff = maxoff;
		maxoff++;
	}
	else
		keyvaltab[0].maxfrqoff = -1;
	num_key++;
	curr_valoff += strlen(cd->value) + 1;
	lastkey = cd->key;
	cd = cd->next;
	if (strcmp(currkey, lastkey) != 0)
		mp = mp->next;
	i++;
	/* ******************** */

	while (cd) {
		currkey = cd->key;

		if (currkey[0] != lastkey[0]) {
			/* update fastidx */
			fastidx[idx(currkey[0])].offset = i;
			for (j = idx(currkey[0])-1; j > firstidx; j--) {
				if (fastidx[j].offset != 0)
					break;
				fastidx[j].offset = i;
			}
			maxkey = currkey[0];
		}
		keyvaltab[i].maxfrqoff = -1;

#if DEBUG_CIN
		pf = fopen("/root/debug.input", "a");
		fprintf(pf, "current key : %s\n", currkey);
		fclose(pf);
#endif
		if (strcmp(currkey, lastkey) != 0) {
			/* update curr_keyoff */
			curr_keyoff += strlen(lastkey) + 1;
			num_key++;
			if (mp->maxPrio > 1 * PRIO_CELL) {
				keyvaltab[i].maxfrqoff = maxoff;
				maxoff++;
			}
			mp = mp->next;
		}
		else {
#if DEBUG_CIN
			pf = fopen("/root/debug.input", "a");
			fprintf(pf, "num_key : %d\n", num_key);
			if (mp == NULL)
				fprintf(pf, "mp NULL ... \n");
			fclose(pf);
#endif
			if (mp->maxPrio > 1 * PRIO_CELL) {
				keyvaltab[i].maxfrqoff = maxoff;
			}
		}

		keyvaltab[i].keyoff = curr_keyoff;
		keyvaltab[i].valoff = curr_valoff;
		keyvaltab[i].frqoff = keyvaltab[i-1].frqoff + 1;
		
		curr_valoff += strlen(cd->value) + 1;
		lastkey = cd->key;
		cd = cd->next;
		i++;
	}
	fastidx[idx(maxkey+1)].offset = i;
#if DEBUG_CIN
	pf = fopen("/root/debug.input", "a");
	fprintf(pf, "The last maxoff : %d\n", maxoff);
	//fprintf(pf, "num_key : %d\n", num_key);
	fclose(pf);
#endif
	num_max = maxoff;

	idx_key_tbl_len = curr_keyoff + strlen(currkey) + 1;
}

int
parse_cinfile(FILE *cinfile)
{
	char cmd[64], arg[64];
	char *argptr;
	int c;
	
	while (!feof(cinfile)) {
		c = fgetc(cinfile);
		if (c == '#') {
			while (fgetc(cinfile) != '\n');
			continue;
		}
		ungetc(c, cinfile);
		if (fscanf(cinfile, "%s ", cmd) != 1) {
			if (!feof(cinfile))
				printf("cin parse error\n");
			break;
		}
		argptr = arg;
		c = fgetc(cinfile);
		if (c == '"') {
			while ((c = fgetc(cinfile)) && c != '"') {
				*argptr = c;
				argptr++;
			}
			*argptr = 0;
		} else {
			do {
				*argptr = c;
				argptr++;
			} while ((c = fgetc(cinfile)) && c != ' ' && c != '\n');
			*argptr = 0;
		}
		
		if (!strcmp("%ename", cmd))
			handle_ename(cinfile, arg);
		else if (!strcmp("%cname", cmd))
			handle_cname(cinfile, arg);
		else if (!strcmp("%preedit_max", cmd))
			handle_preedit_max(cinfile, arg);
		else if (!strcmp("%keyname", cmd))
			handle_keyname(cinfile, arg);
		else if (!strcmp("%chardef", cmd))
			handle_chardef(cinfile, arg);
	}
	
	return TRUE;
}

int
write_tabfile(FILE *tabfile)
{
	CharDef *cd = chardef;
	char *lastkey, *currkey;
	
	fwrite(ename, 20, 1, tabfile);
	fwrite(cname, 20, 1, tabfile);
	fwrite(&hints, sizeof(hints), 1, tabfile);
	fwrite(&preedit_max, sizeof(int), 1, tabfile);
	fwrite(&num_key, sizeof(int), 1, tabfile);
	fwrite(&num_val, sizeof(int), 1, tabfile);
	fwrite(&idx_key_tbl_len, sizeof(int), 1, tabfile);
	fwrite(&idx_val_tbl_len, sizeof(int), 1, tabfile);

	fwrite(pekey, sizeof(PEKey), 0x80-0x20, tabfile);

	fwrite(fastidx, sizeof(FastIdx), 0x80-0x20, tabfile);

	fwrite(keyvaltab, sizeof(KeyValTable), num_val, tabfile);

	lastkey = "";
	while (cd) {
		currkey = cd->key;
		if (strcmp(lastkey, currkey) != 0) {
			lastkey = currkey;
			fwrite(currkey, strlen(currkey)+1, 1, tabfile);
		}
		cd = cd->next;
	}

	cd = chardef;
	while (cd) {
		fwrite(cd->value, strlen(cd->value)+1, 1, tabfile);
		cd = cd->next;
	}

	return TRUE;
}

int
write_frqfile(char *fName)
{
	FILE *pf;
	int i;
	MaxPrio *mp = maxprio;
#if DEBUG_CIN
	FILE *fde;
#endif
	pf = fopen(fName, "w");
	if ((!pf) || (!maxprio))
		return FALSE;

	fwrite(&num_max, sizeof(int), 1, pf);
#if DEBUG_CIN
	fde = fopen("/root/debug.input", "a");
	fprintf(fde, "num max frq : %d\n", num_max);
	fclose(fde);
#endif

	while (mp) {
		if (mp->maxPrio > 1 * PRIO_CELL) {
			fwrite(&(mp->maxPrio), sizeof(int), 1, pf);
		}
		mp = mp->next;
	}

	//for (i=0; i<num_val; i++)
	//	fwrite(priority+i, sizeof(Priority), 1, pf);

	fwrite(priority, num_val * sizeof(Priority), 1, pf);
#if DEBUG_CIN
	fde = fopen("/root/debug.input", "a");
	for (i=0; i<num_val; i++)
		fprintf(fde, "priority %d : %d\n", i, priority[i]);
	fclose(fde);
#endif
	fclose(pf);
}

void
print_usage(void)
{
	printf("Syntax: %s cinfile [-o tabfile] [-e encoding] [-s]\n\n", exec_name);
	printf("-o	Specify output file location\n");
	printf("-e	Input file encoding. Allowed values: UTF-8, GB2312, BIG5HKSCS(default)\n");
	printf("-s	Define SHOW_AS_U_GO\n");
	printf("-f	Also output FREQ table\n");
}

int
main(int argc, char *argv[])
{
	FILE *cinfile = NULL, *tabfile = NULL;
	char *cinfile_name = NULL, *tabfile_name = NULL, *frqfile_name = NULL;
	char *encoding = NULL;
	int c;
	int frq = 0; 
	
	exec_name = strrchr(argv[0], '/');
	if (!exec_name)
		exec_name = argv[0];
	else
		++exec_name;

	cinfile_name = argv[1];

	if (argc < 2) {
		fprintf(stderr, "E: need filename\n");
		print_usage();
		exit(1);
	}

	cinfile = fopen(cinfile_name, "r");
	if (!cinfile) {
		fprintf(stderr, "E: cannot open %s\n", cinfile_name);
		exit(1);
	}

	while ((c = getopt(argc, argv, "e:o:s")) != -1) {
		switch (c) {
			case 'e':
				encoding = strdup(optarg);
				break;
			case 'o':
				tabfile_name = strdup(optarg);
				break;
			case 's':
				hints |= SHOW_AS_U_GO;
				break;
			case '?':
				print_usage();
				exit(0);
				break;
		}
	}

	if (!tabfile_name) {
		if (!strstr(cinfile_name, ".cin")) {
			cinfile_name = strncat(cinfile_name, ".tab", 4);
			tabfile_name = strdup(cinfile_name);
		} else {
			tabfile_name = strdup(cinfile_name);
			strncpy(strrchr(tabfile_name, '.')+1, "tab", 3);
		}
	}

	if (strstr(cinfile_name, "pinyin") || strstr(cinfile_name, "wubi"))
		frq = 1;

	if (frq) {
		if (!strstr(cinfile_name, ".cin")) { // && !strstr(cinfile_name, ".cin")) {
			cinfile_name = strncat(cinfile_name, ".frq", 4);
			frqfile_name = strdup(frqfile_name);
		}
		else {
			frqfile_name = strdup(cinfile_name);
			strncpy(strrchr(frqfile_name, '.')+1, "frq", 3);
		}
	}

	tabfile = fopen(tabfile_name, "w");
	if (!tabfile) {
		fclose(cinfile);
		fprintf(stderr, "E: cannot write to %s\n", tabfile_name);
		exit(1);
	}

	if ((fscanf(cinfile, "%s ", ename) != 1) ||
	    (strcmp("%gen_inp", ename)!=0)) {
		fclose(cinfile);
		fclose(tabfile);
		fprintf(stderr, "E: parse error\n");
		exit(1);
	}

	if (!encoding || !strcasecmp(encoding, "BIG5") || !strcasecmp(encoding, "BIG5HKSCS")) {
		iconvd = iconv_open("UTF-8", "BIG5HKSCS");
		fprintf(stderr, "input encoding is Big5-HKSCS\n");
	} else {
		if (!strcasecmp(encoding, "GB2312") || !strcasecmp(encoding, "GB18030")) {
			iconvd = iconv_open("UTF-8", "GB18030");
			fprintf(stderr, "input encoding is GB18030\n");
		} else {
			iconvd = iconv_open("UTF-8", "UTF-8");
			need_iconv = 0;
			fprintf(stderr, "input encoding is UTF-8\n");
        }
	}

/*
	if (iconvd == 0) {
		fprintf(stderr, "iconvd : iconvd == 0\n");
		return 0;
	}
	else return 0;
*/
	/* write signature */
	fwrite(IM_TBL_SIG, strlen(IM_TBL_SIG)+1, 1, tabfile);

	parse_cinfile(cinfile);
	create_frq();
	create_idx();
#if DEBUG_CIN
	pf = fopen("/root/debug.input", "a");
	fprintf(pf, "CIN traying ... !\n");
	fclose(pf);
#endif
	write_tabfile(tabfile);
	if (frq)
		write_frqfile(frqfile_name);

	iconv_close(iconvd);

	fclose(cinfile);
	fclose(tabfile);
	return 0;
}

// vi:ts=4:sw=4:nowrap:cin:expandtab
